//------------------------------------------------------------------------------
//SFWK Framework
//Copyright (C) 2011 SQLI

//This program is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with this program.  If not, see <http://www.gnu.org/licenses/>.
//------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Data.Objects;
using System.Linq.Expressions;


namespace SFWK.Core.Linq
{
//public static class QueryResultCache
//{
//    /// <summary>
//    /// Returns the result of the query; if possible from the cache, otherwise
//    /// the query is materialized and the result cached before being returned.
//    /// The cache entry has a one minute sliding expiration with normal priority.
//    /// </summary>
//    public static IEnumerable<T> FromCache<T>(this IQueryable<T> query)
//    {
//        return query.FromCache(CacheItemPriority.Normal, TimeSpan.FromMinutes(1));
//    }

//    /// <summary>
//    /// Returns the result of the query; if possible from the cache, otherwise
//    /// the query is materialized and the result cached before being returned.
//    /// </summary>
//    public static IEnumerable<T> FromCache<T>(this IQueryable<T> query,
//        CacheItemPriority priority,
//        TimeSpan slidingExpiration)
//    {

//        // locally evaluate as much of the query as possible
//        var expression = Evaluator.PartialEval(
//            query.Expression,
//            QueryResultCache.CanBeEvaluatedLocally);

//        // use the string representation of the query for the cache key
//        string key = expression.ToString();

//        // the key is potentially very long, so use an md5 fingerprint
//        // (fine if the query result data isn't critically sensitive)
//        key = key.ToMd5Fingerprint();

//        // try to get the query result from the cache
//        var result = HttpRuntime.Cache.Get(key) as List<T>;

//        if (result == null)
//        {
//            // todo: ... ensure that the query results do not
//            // hold on to resources for your particular data source
//            //
//            // for entity framework queries, set to NoTracking
//            var entityQuery = query as ObjectQuery<T>;
//            if (entityQuery != null)
//            {
//                entityQuery.MergeOption = MergeOption.NoTracking;
//            }

//            // materialize the query
//            result = query.ToList();

//            HttpRuntime.Cache.Insert(
//                key,
//                result,
//                null, // no cache dependency
//                Cache.NoAbsoluteExpiration,
//                slidingExpiration,
//                priority,
//                null); // no removal notification
//        }

//        return result;
//    }

//    static Func<Expression, bool> CanBeEvaluatedLocally
//    {
//        get
//        {
//            return expression =>
//                {
//                    // don't evaluate parameters
//                    if (expression.NodeType == ExpressionType.Parameter)
//                        return false;

//                    // can't evaluate queries
//                    if (typeof(IQueryable).IsAssignableFrom(expression.Type))
//                        return false;

//                    return true;
//                };
//        }
//    }

//    /// <summary>
//    /// Creates an MD5 fingerprint of the string.
//    /// </summary>
//    static string ToMd5Fingerprint(this string s)
//    {
//        var bytes = Encoding.Unicode.GetBytes(s.ToCharArray());
//        var hash = new MD5CryptoServiceProvider().ComputeHash(bytes);

//        // concat the hash bytes into one long string
//        return hash.Aggregate(new StringBuilder(32),
//            (sb, b) => sb.Append(b.ToString("X2")))
//            .ToString();
//    }
//}

/// <summary>
/// Enables the partial evalutation of queries.
/// From http://msdn.microsoft.com/en-us/library/bb546158.aspx
/// </summary>
public static class ExpressionEvaluator
{
    /// <summary>
    /// Performs evaluation & replacement of independent sub-trees
    /// </summary>
    /// <param name="expression">The root of the expression tree.</param>
    /// <param name="fnCanBeEvaluated">A function that decides whether a given expression node can be part of the local function.</param>
    /// <returns>A new tree with sub-trees evaluated and replaced.</returns>
    public static Expression PartialEval(Expression expression, Func<Expression, bool> fnCanBeEvaluated)
    {
        return new SubtreeEvaluator(new Nominator(fnCanBeEvaluated).Nominate(expression)).Eval(expression);
    }

    /// <summary>
    /// Performs evaluation & replacement of independent sub-trees
    /// </summary>
    /// <param name="expression">The root of the expression tree.</param>
    /// <returns>A new tree with sub-trees evaluated and replaced.</returns>
    public static Expression PartialEval(this Expression expression)
    {
        return PartialEval(expression, ExpressionEvaluator.CanBeEvaluatedLocally);
    }

    private static bool CanBeEvaluatedLocally(Expression expression)
    {
        return expression.NodeType != ExpressionType.Parameter;
    }

    /// <summary>
    /// Evaluates & replaces sub-trees when first candidate is reached (top-down)
    /// </summary>
    class SubtreeEvaluator : ExpressionVisitor
    {
        HashSet<Expression> candidates;

        internal SubtreeEvaluator(HashSet<Expression> candidates)
        {
            this.candidates = candidates;
        }

        internal Expression Eval(Expression exp)
        {
            return this.Visit(exp);
        }

        public override Expression Visit(Expression exp)
        {
            if (exp == null)
            {
                return null;
            }
            if (this.candidates.Contains(exp))
            {
                return this.Evaluate(exp);
            }
            return base.Visit(exp);
        }

        //private Expression Evaluate(Expression e)
        //{
        //    if (e.NodeType == ExpressionType.Constant)
        //    {
        //        return e;
        //    }
        //    LambdaExpression lambda = Expression.Lambda(e);
        //    Delegate fn = lambda.Compile();
        //    return Expression.Constant(fn.DynamicInvoke(null), e.Type);
        //}
        /// <summary>
        /// Evaluates an expression
        /// Has been modified to evaluate nullable types
        /// </summary>
        /// <param name="e"></param>
        /// <returns></returns>
        private Expression Evaluate(Expression e)
        {
            if (e.NodeType == ExpressionType.Constant)
            {
                return e;
            }
            LambdaExpression lambda = Expression.Lambda(e);
            Delegate fn = lambda.Compile();
            try
            {
                object invoked = fn.DynamicInvoke(null);
                Expression exp = invoked as Expression;
                if (exp!= null) 
                    return exp.PartialEval();
                else
                    return Expression.Constant(invoked, e.Type);
            }
            catch (System.Reflection.TargetInvocationException tie)
            {
                MemberExpression me = e as MemberExpression;
                if (me!= null && me.Member.Name.ToLower() == "value")
                {
                    Type mt = me.Expression.Type;
                    if (mt.IsGenericType 
                            && mt.GetGenericTypeDefinition().UnderlyingSystemType == typeof(System.Nullable<>))
                        return Expression.Constant(
                                    Activator.CreateInstance(mt.GetGenericArguments()[0])   , 
                                    mt.GetGenericArguments()[0]);
                }
                MethodCallExpression mce = e as MethodCallExpression;
                if (mce != null)
                {
                    Expression ev = Evaluate(mce.Object);
                    ConstantExpression ce = ev as ConstantExpression;
                    if (ce != null && ce.Value == null) {
                        Type tp = ce.Type;
                        if (!tp.IsValueType)
                            return Expression.Call(
                                Expression.Constant("", typeof(String)), mce.Method);
                        else
                            return Expression.Call(
                                Expression.Constant(
                                    Activator.CreateInstance(ce.Type), ce.Type), mce.Method);
                        
                    }
                    return Expression.Call(ev, mce.Method);
                }
                return e;
            }
        }
    }

    /// <summary>
    /// Performs bottom-up analysis to determine which nodes can possibly
    /// be part of an evaluated sub-tree.
    /// </summary>
    class Nominator : ExpressionVisitor
    {
        Func<Expression, bool> fnCanBeEvaluated;
        HashSet<Expression> candidates;
        bool cannotBeEvaluated;

        internal Nominator(Func<Expression, bool> fnCanBeEvaluated)
        {
            this.fnCanBeEvaluated = fnCanBeEvaluated;
        }

        internal HashSet<Expression> Nominate(Expression expression)
        {
            this.candidates = new HashSet<Expression>();
            this.Visit(expression);
            return this.candidates;
        }

        public override Expression Visit(Expression expression)
        {
            if (expression != null)
            {
                bool saveCannotBeEvaluated = this.cannotBeEvaluated;
                this.cannotBeEvaluated = false;
                base.Visit(expression);
                if (!this.cannotBeEvaluated)
                {
                    if (this.fnCanBeEvaluated(expression))
                    {
                        this.candidates.Add(expression);
                    }
                    else
                    {
                        this.cannotBeEvaluated = true;
                    }
                }
                this.cannotBeEvaluated |= saveCannotBeEvaluated;
            }
            return expression;
        }
    }
}

}

